package org.shagin.xml;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.shagin.jfin.core.Debug;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * The Class XMLUtils.
 */
public class XMLUtils {
    private static final String TEXT = "#text";
    private static final String ID = "id";
    private static final String DELIMETER = "/";
    private static final Logger log = LoggerFactory.getLogger(XMLUtils.class);

    /**
     * The main method.
     * 
     * @param args the arguments
     * @throws Exception the exception
     */
    public static void main(String[] args) throws Exception {
        File file = new File("data.xml");
        DocumentBuilder builder = DocumentBuilderFactory.newInstance()
                .newDocumentBuilder();
        Document doc = builder.parse(file);

        NodeList nodes = doc.getElementsByTagName("topic");
        for (int i = 0; i < nodes.getLength(); i++) {
            Element element = (Element) nodes.item(i);
            NodeList title = element.getElementsByTagName("title");
            Element line = (Element) title.item(0);
            System.out.println("Title: " + getCharacterDataFromElement(line));
        }
    }

    /**
     * Gets the character data from element.
     * 
     * @param e the e
     * @return the character data from element
     */
    private static String getCharacterDataFromElement(Element e) {
        Node child = e.getFirstChild();
        if (child instanceof CharacterData) {
            CharacterData cd = (CharacterData) child;
            return cd.getData();
        }
        return "";
    }

    /**
     * Gets the document builder.
     * 
     * @return the document builder
     * @throws ParserConfigurationException the parser configuration exception
     */
    public static DocumentBuilder getDocumentBuilder()
            throws ParserConfigurationException {
        return DocumentBuilderFactory.newInstance().newDocumentBuilder();
    }

    /**
     * New document.
     * 
     * @return the document
     * @throws ParserConfigurationException the parser configuration exception
     */
    public static Document newDocument() throws ParserConfigurationException {
        Document doc = getDocumentBuilder().newDocument();
        return doc;
    }

    /**
     * Load xml document.
     * 
     * @param fileName the file name
     * @return the document
     * @throws ParserConfigurationException the parser configuration exception
     * @throws SAXException the sAX exception
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public static Document loadXmlDocument(String fileName)
            throws ParserConfigurationException, SAXException, IOException {
        return loadXmlDocument(new File(fileName));
    }

    /**
     * Load xml document.
     * 
     * @param file the file
     * @return the document
     * @throws ParserConfigurationException the parser configuration exception
     * @throws SAXException the sAX exception
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public static Document loadXmlDocument(File file)
            throws ParserConfigurationException, SAXException, IOException {
        return getDocumentBuilder().parse(file);
    }

    /**
     * Save xml document.
     * 
     * @param doc the doc
     * @param fileName the file name
     * @throws TransformerException the transformer exception
     */
    public static void saveXmlDocument(Document doc, String fileName)
            throws TransformerException {
        saveXmlDocument(doc, new File(fileName));
    }

    /**
     * Save xml document.
     * 
     * @param doc the doc
     * @param file the file
     * @throws TransformerException the transformer exception
     */
    public static void saveXmlDocument(Document doc, File file)
            throws TransformerException {
        TransformerFactory transformerFactory = TransformerFactory
                .newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(file);
        // Output to console for testing
        // StreamResult result = new StreamResult(System.out);
        transformer.transform(source, result);
    }

    /**
     * Gets the child nodes.
     * 
     * @param node the node
     * @return the child nodes
     */
    public static List<Node> getChildNodes(Node node) {
        List<Node> result = new ArrayList<Node>();

        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node item = nodeList.item(i);
            if (!TEXT.equals(item.getNodeName())) {
                result.add(item);
            }
        }

        return result;
    }

    /**
     * Gets the child nodes.
     * 
     * @param node the node
     * @param childNodeName the child node name
     * @return the child nodes
     */
    public static List<Node> getChildNodes(Node node, String childNodeName) {
        List<Node> result = new ArrayList<Node>();
        List<Node> allNodes = getChildNodes(node);
        for (Node n : allNodes) {
            if (isNodeNameEquals(n, childNodeName))
                result.add(n);
        }

        return result;
    }

    /**
     * Find first child node.
     * 
     * @param node the node
     * @param childNodeName the child node name
     * @return the node
     */
    public static Node findFirstChildNode(Node node, String childNodeName) {
        Node result = null;
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node item = nodeList.item(i);
            if (isNodeNameEquals(item, childNodeName)) {
                result = item;
                break;
            }
        }
        return result;
    }

    /**
     * Gets the child elements.
     * 
     * @param node the node
     * @return the child elements
     */
    public static List<Element> getChildElements(Node node) {
        List<Element> result = new ArrayList<Element>();
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node n = nodeList.item(i);
            if (n instanceof Element)
                result.add((Element) n);
        }
        return result;
    }

    /**
     * Gets the document root.
     * 
     * @param doc the doc
     * @return the document root
     */
    public static Node getDocumentRoot(Document doc) {
        Node result = doc.getDocumentElement();
        return result;
    }

    /**
     * Gets the elements.
     * 
     * @param doc the doc
     * @param path the path
     * @return the elements
     */
    public static List<Element> getElements(Document doc, String path) {
        List<Element> result = new ArrayList<Element>();
        return result;
    }

    /**
     * Checks if is node name equals.
     * 
     * @param node the node
     * @param nodeName the node name
     * @return true, if is node name equals
     */
    public static boolean isNodeNameEquals(Node node, String nodeName) {
        return nodeName.equalsIgnoreCase(node.getNodeName());
    }

    /**
     * Fund node by name.
     * 
     * @param node the node
     * @param nodeName the node name
     * @return the node
     */
    public static Node fundNodeByName(Node node, String nodeName) {
        log.trace("Looking '" + nodeName + "' for '" + node.getNodeName() + "'");
        Node result = null;
        List<Node> childs = getChildNodes(node);
        for (Node n : childs) {
            if (isNodeNameEquals(n, nodeName)) {
                result = n;
                break;
            }
        }
        return result;
    }

    /**
     * Fund node by path.
     * 
     * @param node the node
     * @param nodePath the node path
     * @return the node
     */
    public static Node fundNodeByPath(Node node, String nodePath) {
        String[] nodeNames = splitNodePath(nodePath);
        Node result = null;

        if (nodeNames.length > 0) {
            if (!isNodeNameEquals(node, nodeNames[0]))
                throw new RuntimeException("Incorrect path '" + nodePath
                        + "' for node");
            Node n = node;
            for (int i = 1; i < nodeNames.length; i++) {
                n = fundNodeByName(n, nodeNames[i]);
                if (null == n)
                    break;
            }
            result = n;
        }
        return result;
    }

    /**
     * Split node path.
     * 
     * @param nodePath the node path
     * @return the string[]
     */
    private static String[] splitNodePath(String nodePath) {
        Debug.traceEnter(log, nodePath);
        if (nodePath.startsWith(DELIMETER))
            nodePath = nodePath.substring(DELIMETER.length());
        log.trace("nodePath : '" + nodePath + "'");
        String[] result = nodePath.split(DELIMETER);
        return result;
    }

    public static void setNodeAttribute(Document doc, Element element,
            String name, String value) {
        Attr attr = doc.createAttribute(name);
        attr.setValue(value);
        element.setAttributeNode(attr);
    }

    /**
     * Adds the element.
     *
     * @param doc the doc
     * @param parent the parent
     * @param name the name
     * @return the element
     */
    public static Element addElement(Document doc, Element parent, String name) {
        Element result = doc.createElement(name);
        parent.appendChild(result);
        return result;
    }

    /**
     * Gets the node attribute.
     * 
     * @param node the node
     * @param name the name
     * @param defValue the def value
     * @return the node attribute
     */
    public static String getNodeAttribute(Node node, String name,
            String... defValue) {
        String result = (0 == defValue.length ? null : defValue[0]);
        if (node != null) {
            NamedNodeMap attributes = node.getAttributes();
            if (attributes != null) {
                Node value = attributes.getNamedItem(name);
                if (null != value) {
                    result = value.getNodeValue();
                    return result;
                }

                for (int i = 0; i < attributes.getLength(); i++) {
                    value = attributes.item(i);
                    if (isNodeNameEquals(value, name)) {
                        result = value.getNodeValue();
                        break;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Gets the node id.
     * 
     * @param node the node
     * @return the node id
     */
    public static String getNodeId(Node node) {
        return getNodeAttribute(node, ID);
    }

    /**
     * Gets the node attribute double.
     * 
     * @param node the node
     * @param name the name
     * @param defValue the def value
     * @return the node attribute double
     */
    public static Double getNodeAttributeDouble(Node node, String name,
            Double... defValue) {
        Double result = (0 == defValue.length ? 1d : defValue[0]);

        String s = getNodeAttribute(node, name);
        log.trace(node.getNodeName() + "." + name + " = " + s);
        if (null == s)
            return result;
        return Double.valueOf(s);
    }
}
